import * as THREE from 'three';
import Debug from '../tools/Debug';
import BaseModel from '@/editor/BaseModel';
import { scaleZStrategy } from './Strategies';
import ResourceTracker from '../tools/ResourceTracker';
import PluginManager, { PluginOptionMap, PluginMap } from '../PluginManager';

/**
 * FIXME:
 * 顶棚类型到模型URL的映射，实际应该后端存储
 * 获取模型信息时，传入type，返回url,GreenHouse内部不做转换
 */
const proofTypeToUrl: Record<Editor.ProofType, string> = {
  tip: '3D/building/new/dapeng001.fbx',
  round: '3D/building/new/dapeng002.fbx',
  flat: '3D/building/new/dapeng003.fbx',
};

/** 大棚侧面类型定义*/
export const sideMeshType: Record<keyof Editor.SideMeshType, string> = {
  /** 与顶棚相关的侧面-前面 */
  front: '2',
  /** 与顶棚相关的侧面-后面 */
  back: '4',
  /** 不与顶棚相关的侧面-左面 */
  left: '1',
  /** 左侧湿帘 */
  leftWetCurtain: 'sl',
  /** 不与顶棚相关的侧面-右面 */
  right: '3',
  /** 右侧湿帘 */
  rightWetCurtain: 'sl001',
  /** 顶棚 */
  top: '5',
  /** 底部 */
  bottom: '6',
  /** 遮阳帘 */
  sunShade: '7',
};

/**
 * 大棚模型类，继承自 BaseModel
 * 生命周期
 * - 1.EVENT_BASEMODEL_LOADED (模型加载)
 * - 2.EVENT_BASEMODEL_PLUGINS_LOADED  (插件加载)
 *  @see {@link Editor.PlantAreaManager._manualEmitUpadateEvent}
 * - 2.1 若加载了PlantAreaManager插件 触发:EVENT_CROP_STAFF_UPDATE
 *
 *  @see {@link Editor.DeviceManager._manualEmitUpadateEvent}
 * - 2.2 若加载了DeviceManager插件 触发:EVENT_DEVICE_MANAGER_UPDATE
 */
export default class GreenHouse extends BaseModel {
  private readonly namespace = 'GreenHouse';
  static type = 'greenhouse';
  /** 与顶棚相关的前面  */
  static frontMaterialName = `${sideMeshType.front}_boli`;
  /** 与顶棚相关的后面 */
  static backMaterialName = `${sideMeshType.back}_boli`;

  /** 不与顶棚相关的右面 */
  static rightMaterialName = `${sideMeshType.right}_boli`;
  /** 不与顶棚相关的左面 */
  static leftMaterialName = `${sideMeshType.left}_boli`;

  static topMaterialName = `${sideMeshType.top}_boli`;

  static bottomMaterialName = 'di';

  /** 湿帘 */
  static wetCurtainMaterialName = 'sl';

  /** @deprecated 遮阳帘 */
  static sunshadeMaterialName = 'zheyanglian';

  static sideMeshTypes = sideMeshType;

  /** 顶棚类型 默认圆顶 */
  proofType: Editor.ProofType = 'round';

  /** 是否启用湿帘标识符 */
  enableWetCurtain: boolean = false;

  /** FIXME: 唯一id */
  id: number | string;

  private sideTextureMap = new Map<
    'front' | 'back' | 'left' | 'right' | 'bottom' | 'leftWetCurtain' | 'rightWetCurtain',
    { url: string; opacity?: number }
  >();

  cloneProofTracker = new ResourceTracker();

  controls: Editor.Controls = null;

  pluginManager: PluginManager = null;

  /**
   * @param option - 模型配置选项
   */
  constructor(option: Editor.GreenHouseOption) {
    const { proofType = 'round', enableWetCurtain = false, controls, sideTextureArr = [], id = Date.now(), ...baseModelOptionWithoutUrl } = option;
    // 自动推导url
    baseModelOptionWithoutUrl.url = proofTypeToUrl[proofType];
    super(baseModelOptionWithoutUrl);
    this.controls = controls;
    this.id = id;

    this.proofType = proofType;
    /** MARK: 需要注意下方函数会在changeUrl时再次触发 */
    this.on(BaseModel.EVENT_BASEMODEL_LOADED, (model: THREE.Object3D | null) => {
      if (model) {
        // 湿帘状态修正
        this.setWetCurtainState(enableWetCurtain);
        // 纹理预渲染修正
        this.changeAroundTexture(sideTextureArr);

        // // 调整一次遮阳帘材质
        // this.adjustSunShade();
        // // 避免round情况下遮阳帘检测错误
        // const sunShadeMesh = this.model.getObjectByName(sideMeshType.sunShade);
        // sunShadeMesh.position.y -= 5;

        // 防止重复加载
        if (this.pluginManager) return;
        const { plugins, scene } = option;
        // 插件化惰性加载
        if (Array.isArray(plugins)) {
          plugins.forEach((plugin) => {
            const { name } = plugin;
            if (name === 'PlantAreaManager') {
              // 此时需要扩展 builidng  +  scene + controls
              const PlantAreaManagerOption = plugin.option as PluginOptionMap['PlantAreaManager'];
              const _option = Object.assign(PlantAreaManagerOption, {
                scene,
                controls: this.controls,
                building: this,
              }) as Editor.PlantAreaManagerOption;
              plugin.option = _option;
            }
            if (name === 'DeviceManager') {
              // 此时需要扩展 builidng  +  scene + controls
              const deviceManagerOption = plugin.option as PluginOptionMap['DeviceManager'];
              const _option = Object.assign(deviceManagerOption, {
                scene,
                controls: this.controls,
                building: this,
              }) as Editor.DeviceManagerOption;
              plugin.option = _option;
            }
          });
          this.pluginManager = new PluginManager(plugins);
          this.pluginManager.on(PluginManager.EVENT_PLUGINS_LOADED, (plugins) => {
            this._emitEvent(BaseModel.EVENT_BASEMODEL_PLUGINS_LOADED, plugins);
            // MARK: 手动触发一次所有已加载插件的更新事件用以保证预渲染的正确性
            Object.keys(plugins).forEach((pluginName: keyof PluginMap) => {
              const manager = this.getPluginByName(pluginName);
              manager._manualEmitUpadateEvent();
            });
          });
        }
      }
    });
  }

  /**
   * 计算并设置侧面贴图
   * @param texture - 纹理对象
   */
  _computedAndsetSideTexture(texture: THREE.Texture): THREE.Texture {
    if (!this.model || !texture) return texture;
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set(Math.floor(this.scale.z), 1);
    texture.needsUpdate = true;
    return texture;
  }

  /**
   * 改变大棚屋顶类型
   * @param proofType - 顶棚类型
   */
  async changeProofType(proofType: Editor.ProofType): Promise<void> {
    const url = proofTypeToUrl[proofType];
    if (!url) return;
    // 加载新模型
    this.changeUrl(url);
    // 更新proofType
    this.proofType = proofType;
    // TODO:应用当前的texture，先不写
  }

  /** 设置屋顶透明度，默认为1 */
  setProofOpacity(opacity: number = 1): void {
    this.model.traverse((obj: THREE.Mesh) => {
      if (obj.isMesh && obj.name.startsWith(`${sideMeshType.top}`)) {
        if (Array.isArray(obj.material)) {
          obj.material.forEach((mat) => {
            if (mat.name === GreenHouse.topMaterialName) {
              mat.opacity = opacity;
              opacity === 1 ? (mat.transparent = false) : (mat.transparent = true);
              mat.needsUpdate = true;
            }
          });
        }
      }
    });
  }

  /** 清除所有克隆屋顶 */
  private _clearAllCloneProof() {
    const waitToRemoves: Array<THREE.Mesh> = [];
    this.model.traverse((obj: THREE.Mesh) => {
      if (obj.isMesh && obj.name.startsWith(`${sideMeshType.top}-`)) {
        waitToRemoves.push(obj);
      }
    });
    waitToRemoves.forEach((mesh) => {
      this.model.remove(mesh);
    });
    this.cloneProofTracker.dispose();
  }

  /**
   * 改变模型缩放(TODO: Z轴不支持非整数)
   * @param scaleOption - 缩放选项
   */
  override changeScale(scaleOption: Editor.BaseModelScale): void {
    this.scale = Object.assign(this.scale, scaleOption);
    const { base, x, y, z } = this.scale;
    this.model.scale.set(base * x, base * y, base);
    this._clearAllCloneProof();
    /** 自定义Z缩放，最后该值保存在scale中，但是不允许直接缩放模型Z轴 */
    scaleZStrategy({
      model: this.model,
      zScale: z,
      cloneProofTracker: this.cloneProofTracker,
    });
    Debug.log(this.namespace, 'changeScale', this.model);
    this._emitEvent(BaseModel.EVENT_BASEMODEL_SCALE_CHANGED, this.scale);
  }

  /**
   * FIXME: 若url目标url相同，则不实际触发
   * 更换模型四周贴图
   *  - 注意两侧wetcurtain公用一份material，修改时会同步修改
   * @param option - 贴图选项
   */
  changeAroundTexture(options: Editor.SideTextureArr): void {
    if (!Array.isArray(options) || options.length === 0) return;

    let materialHandlers = new Map();

    // 构建材质处理映射
    options.forEach(({ sideTypeKey, url, opacity = 1 }) => {
      if (!url) return;

      const loader = new THREE.TextureLoader();

      // 设置handler
      // 前后面替换
      if (sideTypeKey === 'front' || sideTypeKey === 'back') {
        const aimMaterialName = sideTypeKey === 'front' ? GreenHouse.frontMaterialName : GreenHouse.backMaterialName;
        const frontBackHandler = (mat: THREE.MeshPhongMaterial) => {
          if (mat.name === aimMaterialName) {
            const texture = loader.load(url, () => {
              opacity === 1 ? (mat.transparent = false) : (mat.transparent = true);
              mat.opacity = opacity;
              mat.map = texture;
              mat.needsUpdate = true;
            });
            // 更新sideTextureMap
            this.sideTextureMap.set(sideTypeKey, { url, opacity });
          }
        };

        materialHandlers.set(aimMaterialName, frontBackHandler);
        // 左右面 | 湿帘面 替换
      } else if (sideTypeKey === 'left' || sideTypeKey === 'right' || sideTypeKey === 'leftWetCurtain' || sideTypeKey === 'rightWetCurtain') {
        let aimMaterialName = '';
        if (sideTypeKey === 'leftWetCurtain' || sideTypeKey === 'rightWetCurtain') {
          aimMaterialName = GreenHouse.wetCurtainMaterialName;
        } else {
          aimMaterialName = sideTypeKey === 'left' ? GreenHouse.leftMaterialName : GreenHouse.rightMaterialName;
        }

        const sideHandler = (mat: THREE.MeshPhongMaterial) => {
          if (mat.name === aimMaterialName) {
            const texture = loader.load(url, () => {
              mat.map = texture;
              opacity === 1 ? (mat.transparent = false) : (mat.transparent = true);
              mat.opacity = opacity;
              mat.needsUpdate = true;
              this._computedAndsetSideTexture(mat.map);
              // 更新textureMap
              this.sideTextureMap.set(sideTypeKey, { url, opacity });
            });
          }
        };

        materialHandlers.set(aimMaterialName, sideHandler);
      } else if (sideTypeKey === 'bottom') {
        const bottomHandler = (mat: THREE.MeshPhongMaterial) => {
          if (mat.name === GreenHouse.bottomMaterialName) {
            const texture = loader.load(url, () => {
              texture.name = GreenHouse.bottomMaterialName;
              mat.map = texture;
              mat.needsUpdate = true;
            });
            //  更新textureMap
            this.sideTextureMap.set(sideTypeKey, { url, opacity });
          }
        };

        materialHandlers.set(GreenHouse.bottomMaterialName, bottomHandler);
      }
    });

    if (materialHandlers.size === 0) return;

    this.model.traverse((obj: THREE.Mesh) => {
      if (obj.isMesh) {
        // 归一化处理
        const materials = Array.isArray(obj.material) ? obj.material : [obj.material];
        materials.forEach((mat: THREE.MeshPhongMaterial) => {
          if (mat.map && materialHandlers.has(mat.name)) {
            materialHandlers.get(mat.name)(mat);
          }
        });
      }
    });

    materialHandlers.clear();
    materialHandlers = null;
  }

  /**
   * @deprecated 调整遮阳帘开启程度,暂时不用
   * @param percent - 开启百分比 0~1
   */
  adjustSunShade(percent: number = 0): void {
    const objectMesh = this.model.getObjectByName(sideMeshType.sunShade);
    // 将其y值向下移动5

    if (objectMesh) {
      objectMesh.traverse((obj: any) => {
        if (obj.isMesh && obj.material && obj.material.name === GreenHouse.sunshadeMaterialName) {
          // 使用贴图偏移
          const offsetFlow = (texture: THREE.Texture): void => {
            texture.wrapS = THREE.RepeatWrapping;
            texture.wrapT = THREE.RepeatWrapping;
            texture.repeat.set(0.5, 1);
            texture.offset.set(0.5 - percent / 2, 0);
          };

          const loader = new THREE.TextureLoader();
          if (!obj.material.map) {
            loader.load('3D/building/sunshade.png', (texture: THREE.Texture) => {
              texture.name = GreenHouse.sunshadeMaterialName;
              obj.material.transparent = true;
              obj.material.opacity = 0.99;
              obj.material.side = THREE.DoubleSide;
              offsetFlow(texture);
              obj.material.map = texture;
              obj.material.needsUpdate = true;
            });
          } else {
            offsetFlow(obj.material.map);
          }
        }
      });
    } else {
      console.error('未匹配到遮阳帘mesh');
    }
  }

  /**
   *  开启屋顶控制
   *   - 若启用该功能，在Building类初始化时传入控制器
   *   - 与 disableAdjustProof 成对调用
   */
  enableAdjustProof() {
    if (this.controls === null) return;
    const proofs = [];
    this.model.traverse((child: THREE.Mesh) => {
      if (child.isObject3D && child?.name.startsWith(sideMeshType.top)) {
        proofs.push(child);
      }
    });
    if (proofs.length === 0) return;
    // 启用开启proof
    this.controls.enableClickPicker(proofs);
    this.controls.transformControls.setMode('rotate');
    // TODO: 标记所有开启的proof
  }

  disableAdjustProof() {
    if (this.controls === null) return;
    this.controls.disableClickPicker();
  }

  /** 设置湿帘状态，默认为关 */
  setWetCurtainState(state: boolean) {
    state = state || false;
    const leftWetCurtain = this.model.getObjectByName(sideMeshType.leftWetCurtain);
    const rightWetCurtain = this.model.getObjectByName(sideMeshType.rightWetCurtain);
    leftWetCurtain && (leftWetCurtain.visible = state);
    rightWetCurtain && (rightWetCurtain.visible = state);
    this.enableWetCurtain = state;
  }

  /**
   * 获取侧面网格信息
   * @param type - 侧面类型
   */
  getSideMeshInfo(type: keyof Editor.SideMeshType): { center: THREE.Vector3 } | undefined {
    if (!this.model) return { center: null };
    const sideMesh = this.model.getObjectByName(sideMeshType[type]);
    if (!sideMesh) return undefined;
    const box = new THREE.Box3().setFromObject(sideMesh);
    const center = box.getCenter(new THREE.Vector3());
    return {
      center,
    };
  }

  /**
   * TODO: 将插件相关的三个preload进行挂载返回
   * 保存大棚结构渲染数据
   */
  getSaveData(): Editor.GreenHouseRenderData {
    // 遍历sideTextureArr生成sideTextureArr
    const sideTextureArr = Array.from(this.sideTextureMap.entries()).map(([sideTypeKey, { url, opacity }]) => ({
      sideTypeKey,
      url,
      opacity,
    }));
    return {
      id: this.id,
      scale: this.scale,
      proofType: this.proofType,
      sideTextureArr,
      enableWetCurtain: this.enableWetCurtain,
    };
  }

  /** 根据name获取对应插件manager */
  getPluginByName<T extends keyof PluginMap>(name: T): PluginMap[T] {
    if (!this.pluginManager) return null;
    const { plugins } = this.pluginManager;
    return plugins[name] as PluginMap[T];
  }

  destroy() {
    this._removeEvents();
    // 移除所有克隆屋顶
    this._clearAllCloneProof();
    this.cloneProofTracker = null;
    // 移除所有插件
    this.pluginManager && this.pluginManager.destroy();
    this.pluginManager = null;
    this.baseModelDispose();
  }
}
